Quality Analysis and Classification of Rice using Image Processing¶

IMPORTING DATASETS

In [1]:
import dash
from dash import dcc
from dash import html
import plotly.graph_objects as go
from plotly import subplots
import pandas as pd
import plotly.express as px
import numpy as np
from dash.dependencies import Input, Output, State
import cv2
import PIL.Image as image
from io import BytesIO
import base64
from matplotlib import pyplot as plt
app = dash.Dash(__name__)

classification of rice particals

In [2]:
def get_classification(ratio):
    ratio =round(ratio,1)
    toret=""
    if(ratio>=3 and ratio<3.5):
        toret="Slender"
    elif(ratio>=2.1 and ratio<3):
        toret="Medium"
    elif(ratio>=1.1 and ratio<2.1):
        toret="Bold"
    elif(ratio>0.9 and ratio<=1):
        toret="Round"
    else:
        toret="Dust"
    return toret

initialising the values

In [3]:
classification = {"Slender":0, "Medium":0, "Bold":0, "Round":0, "Dust":0}
avg = {"Slender":0, "Medium":0, "Bold":0, "Round":0, "Dust":0}

load in greyscale mode

In [4]:
from IPython.display import display, Image
img = cv2.imread('C:/Users/lenevo/Downloads/rice.png',0)
display(Image(filename='C:/Users/lenevo/Downloads/rice.png'))

histogram part of the image

In [5]:
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('C:/Users/lenevo/Downloads/rice.png',0)

hist,bins = np.histogram(img.flatten(),256,[0,256])

cdf = hist.cumsum()
cdf_normalized = cdf * hist.max()/ cdf.max()

plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.show()

convert into binary

In [6]:
# 160 - threshold, 255 - value to assign, THRESH_BINARY_INV - Inverse binary
ret,binary = cv2.threshold(img,160,255,cv2.THRESH_BINARY)

averaging filter

In [7]:
kernel = np.ones((5,5),np.float32)/9
dst = cv2.filter2D(binary,-1,kernel)
# -1 : depth of the destination image
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))

erosion

In [8]:
erosion = cv2.erode(dst,kernel2,iterations = 1)

dilation

In [9]:
dilation = cv2.dilate(erosion,kernel2,iterations = 1)

edge detection

In [10]:
edges = cv2.Canny(dilation,100,200)

size detection

In [11]:
contours, hierarchy = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("No. of rice grains=",len(contours))
total_ar=0
No. of rice grains= 30

counting impurities

In [12]:
for cnt in contours:
    x,y,w,h = cv2.boundingRect(cnt)
    aspect_ratio = float(w)/h
    if(aspect_ratio<1):
        aspect_ratio=1/aspect_ratio
    #print(round(aspect_ratio,2),get_classification(aspect_ratio))
    classification[get_classification(aspect_ratio)] += 1
    if get_classification(aspect_ratio) != "Dust":
        total_ar+=aspect_ratio
    if get_classification(aspect_ratio) != "Dust":
        avg[get_classification(aspect_ratio)] += aspect_ratio

getting the average value

In [13]:
avg_ar=total_ar/len(contours)

setting the values for classification of rice

In [14]:
if classification['Slender']!=0:
    avg['Slender'] = avg['Slender']/classification['Slender']
if classification['Medium']!=0:
    avg['Medium'] = avg['Medium']/classification['Medium']
if classification['Bold']!=0:
    avg['Bold'] = avg['Bold']/classification['Bold']
if classification['Round']!=0:
    avg['Round'] = avg['Round']/classification['Round']

saving different types of images

In [15]:
cv2.imwrite('C:/Users/lenevo/Downloads/rice.png', img)
cv2.imwrite('C:/Users/lenevo/Downloads/Binary.png', binary)
cv2.imwrite('C:/Users/lenevo/Downloads/dust.png', dst)
cv2.imwrite('C:/Users/lenevo/Downloads/erosion.png', erosion)
cv2.imwrite('C:/Users/lenevo/Downloads/dialation.png', dilation)
cv2.imwrite('C:/Users/lenevo/Downloads/edges.png', edges)
Out[15]:
True

histogram part for the edge part of the image

In [16]:
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('C:/Users/lenevo/Downloads/edges.png',0)

hist,bins = np.histogram(img.flatten(),256,[0,256])

cdf = hist.cumsum()
cdf_normalized = cdf * hist.max()/ cdf.max()

plt.plot(cdf_normalized, color = 'b')
plt.hist(img.flatten(),256,[0,256], color = 'r')
plt.xlim([0,256])
plt.legend(('cdf','histogram'), loc = 'upper left')
plt.show()

converting rgb to bgr

In [17]:
def readb64(base64_string):
    sbuf = BytesIO()
    sbuf.write(base64.b64decode(base64_string))
    pimg = image.open(sbuf)
    return cv2.cvtColor(np.array(pimg), cv2.COLOR_RGB2BGR)

updating the image

In [18]:
def update_image(pic):
    img = readb64(pic)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    classification1 = {"Slender":0, "Medium":0, "Bold":0, "Round":0, "Dust":0}
    avg1 = {"Slender":0, "Medium":0, "Bold":0, "Round":0, "Dust":0}
    #convert into binary
    ret,binary = cv2.threshold(img,160,255,cv2.THRESH_BINARY)# 160 - threshold, 255 - value to assign, THRESH_BINARY_INV - Inverse binary
    #averaging filter
    kernel = np.ones((5,5),np.float32)/9
    dst = cv2.filter2D(binary,-1,kernel)# -1 : depth of the destination image

    kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))

    #erosion
    erosion = cv2.erode(dst,kernel2,iterations = 1)

    #dilation
    dilation = cv2.dilate(erosion,kernel2,iterations = 1)

    #edge detection
    edges = cv2.Canny(dilation,100,200)

    ### Size detection
    contours, hierarchy = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    #print("No. of rice grains=",len(contours))
    total_ar1=0
    for cnt in contours:
        x,y,w,h = cv2.boundingRect(cnt)
        aspect_ratio = float(w)/h
        if(aspect_ratio<1):
            aspect_ratio=1/aspect_ratio
        print(round(aspect_ratio,2),get_classification(aspect_ratio))
        classification1[get_classification(aspect_ratio)] += 1
        if get_classification(aspect_ratio) != "Dust":
            total_ar1+=aspect_ratio
        if get_classification(aspect_ratio) != "Dust":
            avg1[get_classification(aspect_ratio)] += aspect_ratio
    avg_ar1=total_ar1/len(contours)
    if classification1['Slender']!=0:
        avg1['Slender'] = avg1['Slender']/classification1['Slender']
    if classification1['Medium']!=0:
        avg1['Medium'] = avg1['Medium']/classification1['Medium']
    if classification1['Bold']!=0:
        avg1['Bold'] = avg1['Bold']/classification1['Bold']
    if classification1['Round']!=0:
        avg1['Round'] = avg1['Round']/classification1['Round']
    cv2.imwrite('C:/Users/lenevo/Downloads/rice.png', img)
    cv2.imwrite('C:/Users/lenevo/Downloads/Binary.png', binary)
    cv2.imwrite('C:/Users/lenevo/Downloads/dust.png', dst)
    cv2.imwrite('C:/Users/lenevo/Downloads/erosion.png', erosion)
    cv2.imwrite('C:/Users/lenevo/Downloads/dialation.png', dilation)
    cv2.imwrite('C:/Users/lenevo/Downloads/edges.png', edges)
    return classification1,avg1,avg_ar1

size detection

In [19]:
contours,hierarchy = cv2.findContours(erosion, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print("Total No of grains is= ")
print(len(contours))
total_ar=0
for cnt in contours:
    x,y,w,h = cv2.boundingRect(cnt)
    aspect_ratio = float(w)/h
    if(aspect_ratio<1):
        aspect_ratio=1/aspect_ratio
    print(round(aspect_ratio,2),get_classification(aspect_ratio))
    total_ar+=aspect_ratio
avg_ar=total_ar/len(contours)
print("Average Aspect Ratio= ",round(avg_ar,2),get_classification(avg_ar))
Total No of grains is= 
30
3.17 Slender
1.73 Bold
2.38 Medium
1.07 Bold
1.14 Bold
2.11 Medium
4.0 Dust
1.55 Bold
2.11 Medium
3.5 Dust
2.22 Medium
2.75 Medium
1.14 Bold
3.0 Slender
2.2 Medium
1.91 Bold
2.86 Medium
1.12 Bold
2.86 Medium
3.0 Slender
1.33 Bold
2.11 Medium
2.33 Medium
3.0 Slender
2.38 Medium
1.67 Bold
1.73 Bold
2.62 Medium
2.86 Medium
3.14 Slender
Average Aspect Ratio=  2.3 Medium

displaying image

In [20]:
def get_image(path):
    img=image.open(path)
    # Constants
    img_width = 710
    img_height = 550
    scale_factor = 0.5
    fig = go.Figure()
    fig.add_trace(
        go.Scatter(
            x=[0, img_width * scale_factor],
            y=[0, img_height * scale_factor],
            mode="markers",
            marker_opacity=0
        )
    )
    fig.update_xaxes(
        visible=False,
        range=[0, img_width * scale_factor]
    )
    fig.update_yaxes(
        visible=False,
        range=[0, img_height * scale_factor],
        scaleanchor="x"
    )
    fig.add_layout_image(
        dict(
            x=0,
            sizex=img_width * scale_factor,
            y=img_height * scale_factor,
            sizey=img_height * scale_factor,
            xref="x",
            yref="y",
            opacity=1.0,
            layer="below",
            sizing="stretch",
            source=img)
    )
    fig.update_layout(
        width=img_width * scale_factor,
        height=img_height * scale_factor,
        margin={"l": 0, "r": 0, "t": 0, "b": 0},
    )
    fig.show(config={'doubleClick': 'reset'})
    return fig

average aspect vs classification plot

In [21]:
def get_plot1(classification = classification, avg = avg, avg_ar = avg_ar):
    fig = subplots.make_subplots(rows=1,cols=1,specs=[[{"type":"bar"}]], shared_xaxes=True)
    print(list(classification.keys()))
    print(list(classification.values()))
    plot1 = go.Bar(x=list(classification.keys()), y=list(classification.values()), name="Particles")
    plot2 = go.Bar(x=list(avg.keys()), y=list(avg.values()), name="Avg. Aspect Ratio")
    fig.add_trace(plot1,1,1)
    fig.add_trace(plot2,1,1)
    fig.add_shape(
        type="line",
        x0=0,
        y0=round(avg_ar,2),
        x1=5,
        y1=round(avg_ar,2),
        line=dict(
            color="LightSeaGreen",
            width=4,
            dash="dashdot",
        ),
    )
    fig.update_layout(
        width = 600,
        height = 350,
        margin = {"l": 5, "r": 5, "t": 30, "b": 5},
        title = "Average Aspect Ratio Vs Classification",
        template = "plotly_dark"
    )
    return fig

quality analysis

In [22]:
def get_plot2(classification = classification):
    fig = subplots.make_subplots(rows=1,cols=1,specs=[[{"type":"pie"}]])
    rice = sum(list(classification.values())) - classification['Dust']
    dust = classification['Dust']
    values = [rice, dust]
    labels = ["Rice", "Dust"]
    plot1 = go.Pie(labels=labels, values=values, hole=.3)
    fig.add_trace(plot1,1,1)
    fig.update_layout(
        width = 600,
        height = 350,
        margin = {"l": 65, "r": 5, "t": 60, "b": 50},
        title = "Quality Analysis",
        template = "plotly_dark"
    )
    return fig
In [23]:
#
app.layout = html.Div([
	html.Div([
		html.Div(style={"float":"left","padding" : "5px 0 5px 50px"}),
		html.Div(
			children="Classification and Quality Analysis of Rice",
			style={"float":"left","padding" : "10px 0 10px 10px","font-size": "17px", "font-weight" :"600"}
		),
	],className="nav"),
	html.Div([],style={"height":"50px"},id="home"),
	html.Div([
		html.H1(children="Visualisation of Results", style={"text-align":"center", "margin":"0", "padding-bottom" : "20px", "color" : "blue"}),
		html.Div([
			html.Div([
				dcc.Graph(figure=get_plot1(),id="graph1"),
				html.P("Original Image", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
			html.Div([
				dcc.Graph(figure=get_plot2(),id="graph2"),
				html.P("Binary Image", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
		], style = {"display": "flex", "justify-content": "center", "align-items": "center", "text-align":"center"}),
		html.Div([]),
		html.Div([
			html.Div([
				dcc.Upload([
					'Drag and Drop or ',
					html.A('Select a File')
				],
				style={
					'width': '100%',
					'height': '60px',
					'lineHeight': '60px',
					'borderWidth': '1px',
					'borderStyle': 'dashed',
					'borderRadius': '5px',
					'textAlign': 'center'
				}, id="upload-image"),
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
		], style = {"display": "flex", "justify-content": "center", "align-items": "center", "text-align":"center", "width" : "100%"})
	],style = {"color":"black", "padding" : "20px 0 20px 0", "color" : "black"},id='plots'),
	html.Div([
		html.H1(children="Images", style={"text-align":"center", "margin":"0", "padding-bottom" : "20px"}),
		html.Div([
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/rice.png'),id="img"),
				html.P("Original Image", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/Binary.png'),id="binary"),
				html.P("Binary Image", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/dust.png'),id="dst"),
				html.P("Dust Image", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"})
		], style = {"display": "flex", "justify-content": "center", "align-items": "center", "text-align":"center"}),
		html.Div([]),
		html.Div([
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/erosion.png'),id="erosion"),
				html.P("Erosion", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/dialation.png'),id="dilation"),
				html.P("Dilation", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"}),
			html.Div([
				dcc.Graph(figure=get_image('C:/Users/lenevo/Downloads/edges.png'),id="edges"),
				html.P("Edge Detection", style={"margin":"0","padding-bottom":"10px"})
			], style = {"display": "block", "justify-content": "center", "align-items": "center", "padding":"0 20px 0 20px"})
		], style = {"display": "flex", "justify-content": "center", "align-items": "center", "text-align":"center"})
	],style = {"color":"black", "background-color" : "lightsteelblue", "border-radius":"40px 40px 40px 40px", "padding" : "20px 0 20px 0"},id='images'),
])
['Slender', 'Medium', 'Bold', 'Round', 'Dust']
[5, 13, 10, 0, 2]
In [24]:
#app callback
@app.callback([Output('img', 'figure'),
			   Output('binary', 'figure'),
			   Output('dst', 'figure'),
			   Output('erosion', 'figure'),
			   Output('dilation', 'figure'),
			   Output('edges', 'figure'),
			   Output('graph1', 'figure'),
			   Output('graph2', 'figure')],
			  [Input('upload-image', 'contents')]
)
#updating the outputs
def update_output(list_of_contents):
	if list_of_contents is not None:
		ind = str(list_of_contents).find(",")
		cla,av,av_ar = update_image(list_of_contents[ind:])
		return get_image('C:/Users/lenevo/Downloads/rice.png'), get_image('C:/Users/lenevo/Downloads/Binary.png'), get_image('C:/Users/lenevo/Downloads/dust.png'), get_image('C:/Users/lenevo/Downloads/erosion.png'), get_image('C:/Users/lenevo/Downloads/dialation.png'), get_image('C:/Users/lenevo/Downloads/edges.png'), get_plot1(cla, av, av_ar), get_plot2(cla)
	else:
		return get_image('C:/Users/lenevo/Downloads/rice.png'), get_image('C:/Users/lenevo/Downloads/Binary.png'), get_image('C:/Users/lenevo/Downloads/dust.png'), get_image('C:/Users/lenevo/Downloads/erosion.png'), get_image('C:/Users/lenevo/Downloads/dialation.png'), get_image('C:/Users/lenevo/Downloads/edges.png'), get_plot1(), get_plot2()
In [ ]:
if __name__ == '__main__':
	app.run_server(debug=False)
Dash is running on http://127.0.0.1:8050/

 * Serving Flask app '__main__' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:8050 (Press CTRL+C to quit)
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET /_dash-layout HTTP/1.1" 200 -
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET /_dash-dependencies HTTP/1.1" 200 -
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET /_dash-component-suites/dash/dcc/async-graph.js HTTP/1.1" 304 -
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET /_dash-component-suites/dash/dcc/async-upload.js HTTP/1.1" 304 -
127.0.0.1 - - [01/Jun/2022 13:35:02] "GET /_dash-component-suites/dash/dcc/async-plotlyjs.js HTTP/1.1" 304 -
127.0.0.1 - - [01/Jun/2022 13:35:03] "POST /_dash-update-component HTTP/1.1" 200 -
['Slender', 'Medium', 'Bold', 'Round', 'Dust']
[5, 13, 10, 0, 2]
In [ ]: